package com.lier.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.lier.common.to.es.SkuEsModel;
import com.lier.common.utils.R;
import com.lier.gulimall.search.config.GulimallElasticSearchConfig;
import com.lier.gulimall.search.constant.EsConstant;
import com.lier.gulimall.search.feign.ProductFeignService;
import com.lier.gulimall.search.service.MallSearchService;
import com.lier.gulimall.search.vo.AttrRespVo;
import com.lier.gulimall.search.vo.BrandVo;
import com.lier.gulimall.search.vo.SearchParam;
import com.lier.gulimall.search.vo.SearchResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.*;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author liyd
 * @description
 * @date 2025/7/3 18:05
 */
@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Resource
    private RestHighLevelClient restHighLevelClient;
    @Resource
    private ProductFeignService productFeignService;

    @Override
    public SearchResult search(SearchParam searchParam) {

        SearchResult searchResult = null;

        // 构建检索请求
        SearchRequest searchRequest = buildSearchRequest(searchParam);

        try {
            // 执行检索
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);

            // 分析检索响应数据
            searchResult = buildSearchResult(searchResponse, searchParam);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


        return searchResult;
    }

    /**
     * 构建检索请求
     * @param searchParam 参数
     * @return 检索结果
     */
    private SearchRequest buildSearchRequest(SearchParam searchParam) {

        // 构建dsl语句
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        // 1: 模糊查询, 过滤(属性, 分类, 品牌, 价格区间, 库存)

        BoolQueryBuilder boolQueryBuilder = queryDslBuilder(searchParam);

        // 完成查询条件封装
        searchSourceBuilder.query(boolQueryBuilder);


        // 2: 排序, 分页, 高亮
        if(StringUtils.isNotEmpty(searchParam.getSort())){
            // 排序示例数据: hotScore_desc 标识按照hotScore降序排序
            String[] s = searchParam.getSort().split("_");
            searchSourceBuilder.sort(s[0], Objects.equals(s[1], "desc") ?  SortOrder.DESC : SortOrder.ASC);
        }

        searchSourceBuilder.from((searchParam.getPageNum() - 1) * EsConstant.PRODUCT_PAGE_SIZE);
        searchSourceBuilder.size(EsConstant.PRODUCT_PAGE_SIZE);

        // 高亮
        if(StringUtils.isNotEmpty(searchParam.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        // 3: 聚合分析
        // 品牌聚合
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brandAgg").field("brandId").size(50);
        brandAgg.subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName").size(1));
        brandAgg.subAggregation(AggregationBuilders.terms("brandImgAgg").field("brandImg").size(1));
        searchSourceBuilder.aggregation(brandAgg);

        // 分类聚合
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalogAgg").field("catelogId").size(20);
        catalogAgg.subAggregation(AggregationBuilders.terms("catalogNameAgg").field("catelogName.keyword").size(1));
        searchSourceBuilder.aggregation(catalogAgg);

        // 属性聚合
        NestedAggregationBuilder attrNestedAgg = AggregationBuilders.nested("attrAgg", "attrs");
        // 聚合出所有的id
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId");
        // 聚合出id对应的属性名称和值
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName").size(1));
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue").size(50));

        attrNestedAgg.subAggregation(attrIdAgg);

        searchSourceBuilder.aggregation(attrNestedAgg);

        return new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, searchSourceBuilder);
    }

    /**
     * 解析检索结果
     * @param searchResponse 检索结果
     * @return 解析后的数据
     */
    private SearchResult buildSearchResult(SearchResponse searchResponse, SearchParam searchParam) {
        SearchResult searchResult = new SearchResult();

        // 获取所有命中的数据
        SearchHits hits = searchResponse.getHits();

        // 设置总记录数
        long total = hits.getTotalHits() == null ? 0L : hits.getTotalHits().value;
        searchResult.setTotal(total);

        // 计算总页码
        long totalPages = total % EsConstant.PRODUCT_PAGE_SIZE == 0
                ? (total / EsConstant.PRODUCT_PAGE_SIZE) : (total / EsConstant.PRODUCT_PAGE_SIZE) + 1;

        searchResult.setTotalPages((int) totalPages);

        List<Integer> navs = new ArrayList<>();
        for (int i = 1; i <= totalPages; i++) {
            navs.add(i);
        }

        searchResult.setPageNavs(navs);

        // 设置页码
        searchResult.setPageNum(searchParam.getPageNum());


        // 封装元数据
        List<SkuEsModel> skuEsModels = new ArrayList<>();
        if(hits.getHits() != null) {
            for(SearchHit searchHit : hits.getHits()) {
                String sourceAsString = searchHit.getSourceAsString();
                SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);

                // 设置搜索值高亮
                if(searchParam.getKeyword() != null){
                    HighlightField skuTitle = searchHit.getHighlightFields().get("skuTitle");
                    String content = skuTitle.getFragments()[0].string();
                    skuEsModel.setSkuTitle(content);
                }
                skuEsModels.add(skuEsModel);
            }
        }
        searchResult.setProducts(skuEsModels);

        // 属性封装
        Aggregations aggregations = searchResponse.getAggregations();

        // 分类聚合信息
        ParsedLongTerms catalogAgg = aggregations.get("catalogAgg");
        List<? extends Terms.Bucket> buckets = catalogAgg.getBuckets();

        List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
        for(Terms.Bucket bucket : buckets){
            SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
            String catalogId = bucket.getKeyAsString();
            catalogVo.setCatalogId(Long.valueOf(catalogId));

            ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalogNameAgg");
            catalogVo.setCatalogName(catalogNameAgg.getBuckets().get(0).getKeyAsString());

            catalogVos.add(catalogVo);
        }
        searchResult.setCatalogs(catalogVos);

        // 品牌聚合信息
        ParsedLongTerms brandAgg  = aggregations.get("brandAgg");
        List<? extends Terms.Bucket> brandAggBuckets = brandAgg.getBuckets();

        List<SearchResult.BrandVo> brandVos = new ArrayList<>();
        for(Terms.Bucket brandAggBucket : brandAggBuckets){
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();

            // 设置品牌id
            brandVo.setBrandId(brandAggBucket.getKeyAsNumber().longValue());
            // 设置品牌名称
            ParsedStringTerms brandNameAgg = brandAggBucket.getAggregations().get("brandNameAgg");
            brandVo.setBrandName(brandNameAgg.getBuckets().get(0).getKeyAsString());

            // 设置品牌图片
            ParsedStringTerms brandImgAgg = brandAggBucket.getAggregations().get("brandImgAgg");
            brandVo.setBrandImg(brandImgAgg.getBuckets().get(0).getKeyAsString());

            brandVos.add(brandVo);
        }

        searchResult.setBrands(brandVos);

        // 封装属性信息
        ParsedNested attrAgg = aggregations.get("attrAgg");

        List<SearchResult.AttrVo> attrVos = new ArrayList<>();

        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();

            // 获取属性id
            attrVo.setAttrId(bucket.getKeyAsNumber().longValue());

            // 获取属性名称
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
            attrVo.setAttrName(attrNameAgg.getBuckets().get(0).getKeyAsString());

            // 获取属性值
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
            List<String> attrValues = attrValueAgg.getBuckets().stream()
                    .map(MultiBucketsAggregation.Bucket::getKeyAsString)
                    .collect(Collectors.toList());

            attrVo.setAttrValue(attrValues);
            attrVos.add(attrVo);
        }
        searchResult.setAttrs(attrVos);

        if(searchParam.getAttrs() != null && !skuEsModels.isEmpty()){
            // 构建面包屑导航
            List<SearchResult.NavVo> navVos = searchParam.getAttrs().stream().map(item -> {
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                String[] s = item.split("_");
                navVo.setNavValue(s[1]);

                // 远程获取当前属性内容
                R r = productFeignService.attrInfo(Long.parseLong(s[0]));
                searchResult.getAttrIds().add(Long.parseLong(s[0]));
                if(r.getCode() == 0){
                    AttrRespVo attr = r.getData("attr", new TypeReference<AttrRespVo>() {});
                    if(attr != null){
                        navVo.setNavName(attr.getAttrName());
                    }else {
                        navVo.setNavName(s[0]);
                    }
                }else {
                    navVo.setNavName(s[0]);
                }

                // 设置跳转链接
                String replace = replaceQueryString(searchParam, item, "attrs");
                navVo.setLink("http://search.gulimall.com/list.html?"+replace);
                return navVo;
            }).collect(Collectors.toList());

            searchResult.setNavs(navVos);
        }

        if(searchParam.getBrandId() != null && !searchParam.getBrandId().isEmpty()){
            List<SearchResult.NavVo> resultNavs = searchResult.getNavs();
            SearchResult.NavVo navVo = new SearchResult.NavVo();

            navVo.setNavName("品牌");

            // 远程查询品牌
            R r = productFeignService.brandInfos(searchParam.getBrandId());
            if(r.getCode() == 0){
                List<BrandVo> brands = r.getData("brands", new TypeReference<List<BrandVo>>(){});
                if(brands != null){
                    StringBuilder stringBuilder = new StringBuilder();
                    String replace = "";
                    for(BrandVo brandVo : brands){
                        stringBuilder.append(brandVo.getBrandName()).append(";");
                        replace = replaceQueryString(searchParam, brandVo.getBrandId()+"", "brandId");
                    }
                    navVo.setNavValue(stringBuilder.toString());
                    navVo.setLink("http://search.gulimall.com/list.html?"+replace);
                }
            }
            resultNavs.add(navVo);
        }

        return searchResult;
    }

    private String replaceQueryString(SearchParam searchParam, String attr, String key){
        String encode = null;
        try {
            encode = URLEncoder.encode(attr, "utf-8");
            encode = encode.replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(),e);
        }

        return searchParam.getQueryString().replace("&"+ key +"=" + encode, "");
    }

    /**
     * 查询dsl组装
     * @param searchParam 客户端参数
     * @return bool query dsl 组装对象
     */
    private BoolQueryBuilder queryDslBuilder(SearchParam searchParam) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        if(StringUtils.isNotEmpty(searchParam.getKeyword())){
            boolQuery.must().add(QueryBuilders.matchQuery("skuTitle", searchParam.getKeyword()));
        }

        if(searchParam.getCatalog3Id() != null){
            boolQuery.filter().add(QueryBuilders.termQuery("catelogId", searchParam.getCatalog3Id()));
        }

        if(searchParam.getBrandId() != null && !searchParam.getBrandId().isEmpty()){
            boolQuery.filter().add(QueryBuilders.termsQuery("brandId", searchParam.getBrandId()));
        }

        if(searchParam.getHasStock() != null){
            boolQuery.filter().add(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
        }


        // 价格区间检索1_500(1到500), _500(低于500), 500_(500以上)
        if(StringUtils.isNotEmpty(searchParam.getSkuPrice())){
            String[] s = searchParam.getSkuPrice().split("_");
            RangeQueryBuilder skuPriceQuery = QueryBuilders.rangeQuery("skuPrice");
            if(s.length == 2){
                skuPriceQuery.gte(s[0]).lte(s[1]);
            }else if(s.length == 1){
                if(searchParam.getSkuPrice().startsWith("_")){
                    skuPriceQuery.lte(s[0]);
                }else if(searchParam.getSkuPrice().endsWith("_")) {
                    skuPriceQuery.gte(s[0]);
                }
            }

            boolQuery.filter().add(skuPriceQuery);
        }

        // 属性查询
        if(searchParam.getAttrs() != null && !searchParam.getAttrs().isEmpty()){

            // attr实力示例数据: 1_1080p:2k:4k, 指的是1号属性值为1080p, 2k, 4k
            for(String attr : searchParam.getAttrs()){
                BoolQueryBuilder attrsNestedBoolQuery = QueryBuilders.boolQuery();
                String[] s = attr.split("_");
                // 检索属性id
                String attrId = s[0];
                // 属性对应的值
                String[] attrValues = s[1].split(":");
                attrsNestedBoolQuery.must().add(QueryBuilders.termQuery("attrs.attrId", attrId));
                attrsNestedBoolQuery.must().add(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
                // 每个attr都要生成自己的NestedQuery
                NestedQueryBuilder attrsNestedQuery = QueryBuilders.nestedQuery("attrs", attrsNestedBoolQuery, ScoreMode.None);
                boolQuery.filter().add(attrsNestedQuery);
            }

        }

        return boolQuery;
    }
}
